using System;
using System.Collections.Generic;
using System.Text;
using OhioState.Graphics;
using OhioState.Graphics.Proxy;
using Tao.OpenGl;

namespace OhioState.Graphics.OpenGL
{
    public class ShaderProgramHandler : OpenGLObject, IShaderProgram, IOpenGLResource, IDisposable
    {
        private ShaderProgram _shaderProxy;
        public ShaderProgramHandler(ShaderProgram proxy)
            : base(proxy)
        {
            _shaderProxy = proxy;
        }

        #region IShader Members
        private bool _created = false;
        private ShaderProgramGL _realShader;
        public void MakeActive(IRenderPanel panel)
        {
            if (!_created)
            {
                _realShader = new ShaderProgramGL();
                _created = true;
                this.Invalid = true;
            }
            if (this.Invalid)
            {
                this.Invalid = false;
                Build(panel);
            }
            _realShader.MakeActive(panel);
        }
        public void Deactivate()
        {
            _realShader.Deactivate();
        }
        bool _linkStatus = false;
        public bool Build(IRenderPanel panel)
        {
            if (!_created)
            {
                _realShader = new ShaderProgramGL();
                _created = true;
                this.Invalid = true;
            }
            //
            // The main way a GLSL program is going to be invalid is if
            // the set of routines used changes. Uniform variables are 
            // currently controlled by the material.
            //
            _realShader.RemoveAllRoutines();

            foreach (VertexShader shader in _shaderProxy.VertexList)
            {
                ShaderRoutineHandler handler = panel.ResourceManager.GetItem(shader) as ShaderRoutineHandler;
                if (handler != null)
                {
                    if (handler.Compile(panel))
                    {
                        _realShader.AttachShader(handler);
                    }
                }
            }
            foreach (FragmentShader shader in _shaderProxy.FragmentList)
            {
                ShaderRoutineHandler handler = panel.ResourceManager.GetItem(shader) as ShaderRoutineHandler;
                if (handler != null)
                {
                    if (handler.Compile(panel))
                    {
                        _realShader.AttachShader(handler);
                    }
                }
            }
            _linkStatus = _realShader.Link();
            SetUniformVariableList();
            SetLinkLog();
            return _linkStatus;
        }
        #endregion

        #region IHardwareResource<uint> Members
        public uint GUID
        {
            get { return _realShader.GUID; }
        }
        #endregion

        private void SetUniformVariableList()
        {
            Dictionary<string, UniformVariableInfo> uniformVariables = _shaderProxy.GetUniformVariables();
            // clear all the old contents
            uniformVariables.Clear();
            uint programID = (_realShader as IOpenGLResource).GUID;
            string name = string.Empty;
            System.Text.StringBuilder variableName;
            int num, maxLength, size, length, type;
            int location = -1;
            // get the number of active uniform variables
            Gl.glGetProgramiv(programID, Tao.OpenGl.Gl.GL_ACTIVE_UNIFORMS, out num);
            // get the maximum length among active variables
            Gl.glGetProgramiv(programID, Tao.OpenGl.Gl.GL_ACTIVE_UNIFORM_MAX_LENGTH, out maxLength);
            variableName = new System.Text.StringBuilder(maxLength);
            // go through and update each uniform varible in UniformVariableInfo
            for (uint index = 0; index < num; index++)
            {
                // get length, size, type, and name; size indicates size of array 
                Gl.glGetActiveUniform(programID, index, maxLength, out length, out size, out type, variableName);
                name = System.Convert.ToString(variableName);
                location = Gl.glGetUniformLocation(programID, name);
                // ignore built-in variables (location for built-in variables = -1)
                if (location != -1)
                {
                    UniformVariableInfo.UniformType uniformType = GetVariableType(type);
                    UniformVariableInfo uniform = new UniformVariableInfo(size, uniformType, UniformVariableInfo.FoundInRoutine.DONT_CARE);
                    uniformVariables.Add(name, uniform);
                }
            }
        }

        //
        // Return Uniform Variable type 
        //
        private UniformVariableInfo.UniformType GetVariableType(int type)
        {
            UniformVariableInfo.UniformType _type;
            switch (type)
            {
                case Gl.GL_FLOAT:
                    _type = UniformVariableInfo.UniformType.FLOAT;
                    break;
                case Gl.GL_INT:
                    _type = UniformVariableInfo.UniformType.INT;
                    break;
                case Gl.GL_FLOAT_VEC2:
                    _type = UniformVariableInfo.UniformType.FLOAT_VEC2;
                    break;
                case Gl.GL_INT_VEC2:
                    _type = UniformVariableInfo.UniformType.INT_VEC2;
                    break;
                case Gl.GL_BOOL_VEC2:
                    _type = UniformVariableInfo.UniformType.BOOL_VEC2;
                    break;
                case Gl.GL_FLOAT_VEC3:
                    _type = UniformVariableInfo.UniformType.FLOAT_VEC3;
                    break;
                case Gl.GL_INT_VEC3:
                    _type = UniformVariableInfo.UniformType.INT_VEC3;
                    break;
                case Gl.GL_BOOL_VEC3:
                    _type = UniformVariableInfo.UniformType.BOOL_VEC3;
                    break;
                case Gl.GL_FLOAT_VEC4:
                    _type = UniformVariableInfo.UniformType.FLOAT_VEC4;
                    break;
                case Gl.GL_INT_VEC4:
                    _type = UniformVariableInfo.UniformType.INT_VEC4;
                    break;
                case Gl.GL_BOOL_VEC4:
                    _type = UniformVariableInfo.UniformType.BOOL_VEC4;
                    break;
                case Gl.GL_FLOAT_MAT2:
                    _type = UniformVariableInfo.UniformType.FLOAT_MAT2;
                    break;
                case Gl.GL_FLOAT_MAT3:
                    _type = UniformVariableInfo.UniformType.FLOAT_MAT3;
                    break;
                case Gl.GL_FLOAT_MAT4:
                    _type = UniformVariableInfo.UniformType.FLOAT_MAT4;
                    break;
                case Gl.GL_SAMPLER_1D:
                    _type = UniformVariableInfo.UniformType.SAMPLER_1D;
                    break;
                case Gl.GL_SAMPLER_2D:
                    _type = UniformVariableInfo.UniformType.SAMPLER_2D;
                    break;
                case Gl.GL_SAMPLER_3D:
                    _type = UniformVariableInfo.UniformType.SAMPLER_3D;
                    break;
                case Gl.GL_SAMPLER_CUBE:
                    _type = UniformVariableInfo.UniformType.SAMPLER_CUBE;
                    break;
                case Gl.GL_SAMPLER_1D_SHADOW:
                    _type = UniformVariableInfo.UniformType.SAMPLER_1D_SHADOW;
                    break;
                default:
                    _type = UniformVariableInfo.UniformType.SAMPLER_2D_SHADOW;
                    break;
            }
            return _type;

        }

        private void SetLinkLog()
        {
            StringBuilder log = new StringBuilder(1024);
            log.Append("----- " + "Linking of   ");
            log.Append(_shaderProxy.Name);
            if (_realShader.LinkStatus)
                log.AppendLine("   succeeded! -----");
            else
                log.AppendLine("   failed! -----");
            log.AppendLine(_realShader.LinkLog);
            _shaderProxy.SetLinkInfo(log.ToString());
        }

        #region IDisposable Members

        public override void Dispose()
        {
            base.Dispose();
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (_created)
                {
                    _realShader.Dispose();
                }
            }
        }

        #endregion
    }
}
